package org.gluedom.data;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;

public class ConceptRelation implements Immutable, Identifiable, Ownable,
		PropertyContainer {
	private final Identifier identifier;
	private final Identifier ownerId;
	private final Identifier inConcept;
	private final Identifier outConcept;
	private final List<Property> properties;
	private final String hashedId;

	private volatile int hash = 0;

	private ConceptRelation(Builder builder) {
		identifier = builder.identifier.identifier;
		ownerId = builder.ownerId.ownerId;
		inConcept = builder.inConcept;
		outConcept = builder.outConcept;
		properties = ImmutableList.copyOf(builder.properties.properties);
		hashedId = generateHashedId(identifier, inConcept, outConcept);
	}

	@Override
	public Identifier getIdentifier() {
		return identifier;
	}

	@Override
	public Identifier getOwnerId() {
		return ownerId;
	}

	public Identifier getInConcept() {
		return inConcept;
	}

	public Identifier getOutConcept() {
		return outConcept;
	}

	@Override
	public Iterable<Property> getProperties() {
		return properties;
	}

	@Override
	public String getProperty(String name) {
		return Property.findProperty(name, properties);
	}

	String getHashedId() {
		return hashedId;
	}

	public static class Builder {
		private final Identifiable.Builder identifier;
		private final Ownable.Builder ownerId;
		private Identifier inConcept;
		private Identifier outConcept;
		private final PropertyContainer.Builder properties = new PropertyContainer.Builder();

		public Builder(ConceptRelation relation) {
			identifier = new Identifiable.Builder(relation.identifier);
			ownerId = new Ownable.Builder(relation.ownerId);
			inConcept = relation.inConcept;
			outConcept = relation.outConcept;
			properties.properties(relation.properties);
		}

		public Builder(Identifier identifier, Identifier ownerId,
				Identifier inConcept, Identifier outConcept) {
			this.identifier = new Identifiable.Builder(checkNotNull(identifier));
			this.ownerId = new Ownable.Builder(checkNotNull(ownerId));
			this.inConcept = checkNotNull(inConcept);
			this.outConcept = checkNotNull(outConcept);
		}

		public Builder identifier(Identifier identifier) {
			this.identifier.identifier(checkNotNull(identifier));
			return this;
		}

		public Builder ownerId(Identifier ownerId) {
			this.ownerId.ownerId(checkNotNull(ownerId));
			return this;
		}

		public Builder inConcept(Identifier inConcept) {
			this.inConcept = checkNotNull(inConcept);
			return this;
		}

		public Builder outConcept(Identifier outConcept) {
			this.outConcept = checkNotNull(outConcept);
			return this;
		}

		public Builder property(String name, String value) {
			properties.property(name, value);
			return this;
		}

		public Builder property(Property property) {
			properties.property(checkNotNull(property));
			return this;
		}

		public Builder properties(Collection<Property> properties) {
			this.properties.properties(properties);
			return this;
		}

		public ConceptRelation build() {
			return new ConceptRelation(this);
		}
	}

	static String generateHashedId(Identifier identifier, Identifier inConcept,
			Identifier outConcept) {
		StringBuilder sb = new StringBuilder();
		sb.append(Identifier.enquote(identifier));
		if (inConcept != null) {
			sb.append(":");
			sb.append(Identifier.enquote(inConcept));
			if (outConcept != null) {
				sb.append(":");
				sb.append(Identifier.enquote(outConcept));
			}
		}
		return sb.toString();
	}

	static final Function<ConceptRelation, String> RELATIONHASH_FUNCTION = new Function<ConceptRelation, String>() {
		@Override
		public String apply(ConceptRelation relation) {
			return relation.getHashedId();
		}
	};

	public static final Comparator<ConceptRelation> RELATION_COMPARATOR = Ordering
			.natural().onResultOf(RELATIONHASH_FUNCTION);

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof ConceptRelation))
			return false;
		ConceptRelation other = (ConceptRelation) obj;
		if (hashCode() != other.hashCode())
			return false;
		if (!identifier.equals(other.identifier))
			return false;
		if (!ownerId.equals(other.ownerId))
			return false;
		if (!inConcept.equals(other.inConcept))
			return false;
		if (!outConcept.equals(other.outConcept))
			return false;
		if (!properties.equals(other.properties))
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		if (hash == 0) {
			int result = 9849;
			result = 31 * result + identifier.hashCode();
			result = 31 * result + ownerId.hashCode();
			result = 31 * result + inConcept.hashCode();
			result = 31 * result + outConcept.hashCode();
			result = 31 * result + properties.hashCode();
			hash = result;
		}
		return hash;
	}

	@Override
	public String toString() {
		return hashedId;
	}
}
